// --------------------------------------------------------------------------------------------------------------------
// <copyright file="AbstractChronosWorker.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework
{
    #region

    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Org.OpenTrader.Framework.Interfaces;

    #endregion

    /// <summary>
    /// The chronos_ win mm.
    /// </summary>
    public abstract class AbtractChronosWorker
    {
        #region Constants and Fields

        /// <summary>
        /// The Resolution of this MM Pulsar (millisecond).
        /// </summary>
        protected readonly int Resolution;

        /// <summary>
        /// The Tolerance in time deviation
        /// </summary>
        protected readonly double Tolerance;

        /// <summary>
        /// The brokeTolerance.
        /// </summary>
        private readonly IDictionary<PulsedInfo, int> brokeTolerance = new Dictionary<PulsedInfo, int>();

        /// <summary>
        /// The locker.
        /// </summary>
        private readonly ILocker locker = new Locker();

        /// <summary>
        /// The pulsed.
        /// </summary>
        private readonly SortedArray<PulsedInfo> pulsed = new SortedArray<PulsedInfo>(new PulsedInfo.Comparer());

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="AbtractChronosWorker"/> class.
        /// </summary>
        /// <param name="resolution">
        /// The Resolution.
        /// </param>
        /// <param name="precision">
        /// The precision.
        /// </param>
        public AbtractChronosWorker(int resolution, double precision)
        {
            this.Resolution = resolution;
            this.Tolerance = precision;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// The add.
        /// </summary>
        /// <param name="callableable">
        /// The callableable.
        /// </param>
        /// <exception cref="ArgumentException">
        /// </exception>
        public void Add(PulsedInfo callableable)
        {
            if (callableable.Target.Period.TotalMilliseconds < this.Resolution)
            {
                throw new ArgumentException(
                    String.Format(
                        "This AbtractChronosWorker instance has a Resolution of {0}millisecond cannot pulse this at {1}millisecond", 
                        this.Resolution, 
                        callableable.Target.Period.TotalMilliseconds));
            }

            using (this.locker.Lock())
            {
                if (!this.pulsed.ContainsKey(callableable))
                {
                    callableable.LastPulse = Timings.Now;
                    callableable.NextPulse = callableable.LastPulse + callableable.Target.Period;
                    this.pulsed.AddRange(callableable);
                    this.brokeTolerance[callableable] = new int();
                }
            }
        }

        /// <summary>
        /// The contains.
        /// </summary>
        /// <param name="callableable">
        /// The callableable.
        /// </param>
        /// <returns>
        /// The contains.
        /// </returns>
        public bool Contains(PulsedInfo callableable)
        {
            using (this.locker.Lock())
            {
                return this.pulsed.ContainsKey(callableable);
            }
        }

        /// <summary>
        /// The remove.
        /// </summary>
        /// <param name="callableable">
        /// The callableable.
        /// </param>
        /// <returns>
        /// The remove.
        /// </returns>
        public bool Remove(PulsedInfo callableable)
        {
            using (this.locker.Lock())
            {
                {
                    if (this.pulsed.ContainsKey(callableable))
                    {
                        this.pulsed.Remove(callableable);
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
        }

        /// <summary>
        /// The start.
        /// </summary>
        public abstract void Start();

        /// <summary>
        /// The stop.
        /// </summary>
        public abstract void Stop();

        #endregion

        #region Methods

        /// <summary>
        /// The on timer.
        /// </summary>
        protected void OnTimer()
        {
            Devel.NoThrow(
                delegate
                    {
                        using (this.locker.Lock(3))
                        {
                            if (this.pulsed.Count <= 0)
                            {
                                return;
                            }

                            PulsedInfo nextToPulse = null;

                            // var count = 0;
                            do
                            {
                                var now = Timings.Now;
                                nextToPulse = this.pulsed.Keys.ElementAt(0);
                                if ((nextToPulse.NextPulse - now).TotalMilliseconds <= 0)
                                {
                                    // Remove the nextToPulse from table as we are calling it now
                                    this.pulsed.Remove(nextToPulse);

                                    // Check if we are inside Tolerance limits
                                    // double deviation = Math.Round(100.0 - ((double)(now - nextToPulse.LastPulse).TotalMilliseconds / nextToPulse.Target.Period.TotalMilliseconds) * 100.0, 2);
                                    // if (Math.Abs(deviation) > Tolerance)
                                    // brokeTolerance[nextToPulse]++;

                                    // Reinsert into the sorted array to remain consistent with 
                                    // "nearest itemillisecond to be called are in front of the table"
                                    nextToPulse.LastPulse = now;
                                    nextToPulse.NextPulse = nextToPulse.LastPulse + nextToPulse.Target.Period;

                                    this.Add(nextToPulse);

                                    // Call the IPulsable handler
                                    nextToPulse.Target.Handler.BeginInvoke(null, null);
                                }
                                else
                                {
                                    nextToPulse = null;
                                }
                            }
                            while (nextToPulse != null);
                        }
                    });
        }

        #endregion
    }
}